/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.application;

import java.awt.AWTEvent;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.io.Serializable;

import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;

public class SpringGlassPane extends JPanel implements Serializable { 
 
	private static final long serialVersionUID = -6313212182962014721L;

	private static final float MAGNIFY_FACTOR = 1.5f;
	private final int DURATION = 500;
	
	private float zoom;	
	private Timer timer;	
	private long animationStartTime;
	
	private Rectangle bounds;
	private Image image;	
	private boolean faded = false;
	
	//for loading panel
	private boolean showLoading = false;
	private int ANIMATION_DURATION_LOADING = 1000;
	private Timer timerLoading;
	private long animStartTimeLoading;
	private int xPos = 64;
	private int yPos = 0;
	private ImageIcon inputImage;
	private BufferedImage sourceBI = null;
	private String loadingMsg = null;
	
	
	// event listener used to consume events
	private MouseListener mouseListener = 
		new MouseAdapter(){ };
	private MouseMotionListener mouseMotionListener = 
		new MouseMotionAdapter(){ };
	private MouseWheelListener mouseWheelListener = new MouseWheelListener() {
		@Override
		public void mouseWheelMoved(MouseWheelEvent e) { }
	};
	private AWTEventListener keyAWTEventListener = new AWTEventListener() {
		@Override
		public void eventDispatched(AWTEvent e) {
			KeyEvent ke = (KeyEvent) e;
			ke.consume();
			
		}
	};
	private KeyListener keyListener = new KeyAdapter() { };

	public SpringGlassPane() {
		super(null); 
        setOpaque(false); 
		timer = new Timer(30, new TimerActionListener());
		
		
		inputImage = new ImageIcon(
				getClass().getResource("images/loading.png"));
		sourceBI = new BufferedImage(inputImage.getIconWidth(),
				inputImage.getIconHeight(),BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = sourceBI.createGraphics();
		g.drawImage(inputImage.getImage(), 0, 0, this);
		
		timerLoading = new Timer(30, new TimerLoadingActionListener());
		animStartTimeLoading = 10 + System.nanoTime() / 1000000;
	}
	
	/**
	 * Shows a loading pane if true with faded background. All user inputs will
	 * be blocked (but not yet) and a loading message will be displayed if
	 * not null.
	 * 
	 * If false, the loading pane will not be displayed.
	 * 
	 * @param show
	 */
	public void showLoadingPanel(boolean show) {
		//TODO block all user input.
		showLoading = show;
		faded = show;
		
		if (show) {
			Toolkit.getDefaultToolkit().addAWTEventListener(keyAWTEventListener, 
					AWTEvent.KEY_EVENT_MASK);
			addMouseListener(mouseListener);
			addMouseMotionListener(mouseMotionListener);
			addMouseWheelListener(mouseWheelListener);
			addKeyListener(keyListener);
			
			timerLoading.start();
		}
		else {
			Toolkit.getDefaultToolkit().removeAWTEventListener(
					keyAWTEventListener);
			removeMouseListener(mouseListener);
			removeMouseMotionListener(mouseMotionListener);
			removeMouseWheelListener(mouseWheelListener);
			removeKeyListener(keyListener);
			
			timerLoading.stop();
		}
		
		repaint();
	}
	
	/**
	 * Sets the text for the loading pane.
	 * 
	 * @param msg
	 */
	public void setLoadingMessage(String msg) {
		loadingMsg = msg;
	}
	
	/**
	 * Returns the text for the loading pane.
	 */
	public String getLoadingMessage() {
		return loadingMsg;
	}
	
	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		if ((image == null || bounds == null) && faded == false) {
			return;
		}
		if (image != null || bounds != null) {

		
		int width = image.getWidth(this);
		width += (int) (image.getWidth(this) * MAGNIFY_FACTOR * getZoom());
			
		int height = image.getHeight(this);
		height += (int) (image.getHeight(this) * MAGNIFY_FACTOR * getZoom());
			
		int x = (bounds.width - width) / 2;
		int y = (bounds.height - height) / 2;

		Graphics2D g2 = (Graphics2D) g.create();
		g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
					RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			
		g2.setComposite(AlphaComposite.SrcOver.derive(1.0f - getZoom()));
		g2.drawImage(image, x + bounds.x, y + bounds.y, width, height, null);
		}
		
		//draw the fading effect
		if (faded == true) {
			Graphics2D g2 = (Graphics2D) g.create();
			// gets the current clipping area
			Rectangle clip = g.getClipBounds();
			// sets a  translucent composite
			AlphaComposite alpha = AlphaComposite.Xor.derive(0.55f);
			g2.setComposite(alpha);
			// fills the background
			g2.setColor(getBackground());
			g2.fillRect(clip.x, clip.y, clip.width, clip.height);
		}
		
		//draw the loading pane
		if (showLoading) {
			Graphics2D g2 = (Graphics2D) g.create();
			
			int x = (getWidth() - 200)/2;
			int y = (getHeight() - 150)/2;
			
			g2.setColor(new Color(0xffffff));
			g2.fillRect(x, y, 200, 150);
			
			x = (getWidth() - 32)/2;
			y = (getHeight() - 32)/2;
			g2.drawImage(sourceBI.getSubimage(xPos, yPos, 32, 32), x, y, this);
			
			if (loadingMsg != null) {
				FontMetrics fm = g.getFontMetrics();
				g2.setColor(new Color(0x666666));
				g2.drawString(loadingMsg, 
						(getWidth()-fm.stringWidth(loadingMsg))/2,
						y + fm.getAscent() + 32 + 20);
			}
		}
	}
	
	/**
	 * If true display the glass pane in a dark shine, also all components
	 * under the glass pane. If false displays glass pane normally. 
	 * 
	 * @param faded
	 */
	public void setFaded(boolean faded) {
		this.faded = faded;
		this.repaint();
	}
	
	/**
	 * Shows the animation.
	 * @param bounds
	 * @param image
	 */
	public void showSpring(Rectangle bounds, Image image) {
		this.bounds = bounds;
		this.image = image;
		
		zoom = 0.0f;
		
		if(!timer.isRunning()) {
			animationStartTime = System.nanoTime() / 1000000;
			timer.start();
		}		
		
		repaint();
	}

	public float getZoom() {
		return zoom;
	}

	public void setZoom(float zoom) {
		this.zoom = zoom;
		repaint();
	}
	
	@Override
	public boolean contains(int x, int y) { 
        if (getMouseListeners().length == 0 
        		&& getMouseMotionListeners().length == 0 
                && getMouseWheelListeners().length == 0 
                && getCursor() == Cursor.getPredefinedCursor(
                		Cursor.DEFAULT_CURSOR)) { 
            return false; 
        } 
        return super.contains(x, y); 
    }
	
	/**
	 * This timer is used for animating the spring effect.
	 *
	 */
	private class TimerActionListener implements ActionListener, Serializable {
		private static final long serialVersionUID = -7288432214594110368L;

		@Override
		public void actionPerformed(ActionEvent e) {
			long currentTime = System.nanoTime() / 1000000;
			long totalTime = currentTime - animationStartTime;
			if (totalTime > DURATION) {
					//animationStartTime = currentTime;
					timer.stop();
					zoom = 0.0F;
			}
			float fraction = (float)totalTime / DURATION;
			fraction = Math.min(1.0f, fraction);

			zoom = Math.abs((fraction));
			
			repaint();			
		}		
	}
	
	/**
	 * This timer is used for animating the loading panel.
	 *
	 */
	private class TimerLoadingActionListener implements ActionListener, 
			Serializable {

		private static final long serialVersionUID = 5408829122972822467L;

		@Override
		public void actionPerformed(ActionEvent e) {
			long currentTime = System.nanoTime() / 1000000;
			long totalTime = currentTime - animStartTimeLoading;
			if (totalTime > ANIMATION_DURATION_LOADING) {
				animStartTimeLoading = currentTime;
			}
			
			if (xPos+32 >= sourceBI.getWidth()) {
				xPos = 0;
				if (yPos+32 >= sourceBI.getHeight()) {
					yPos = 0;
					xPos = 64;
				} else {
					yPos += 32;
				}
			} else {
				xPos += 32;
			}
			
			repaint();			
		}		
	}
}